/*
 * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
 * The software in this package is published under the terms of the CPAL v1.0
 * license, a copy of which has been included with this distribution in the
 * LICENSE.txt file.
 */
package org.mule.runtime.core.util;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

/**
 * <code>Multicaster</code> is a utility that can call a given method on a collection of objects that implement one or more common
 * interfaces. The create method returns a proxy that can be cast to any of the the interfaces passed and be used like a single
 * object.
 */
// @ThreadSafe
public final class Multicaster {

  /** Do not instanciate. */
  private Multicaster() {
    // no-op
  }

  public static Object create(Class<?> theInterface, Collection<?> objects) {
    return create(new Class[] {theInterface}, objects);
  }

  public static Object create(Class<?> theInterface, Collection<?> objects, InvokeListener listener) {
    return create(new Class[] {theInterface}, objects, listener);
  }

  public static Object create(Class<?>[] interfaces, Collection<?> objects) {
    return create(interfaces, objects, null);
  }

  public static Object create(Class<?>[] interfaces, Collection<?> objects, InvokeListener listener) {
    return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), interfaces,
                                  new CastingHandler(objects, listener));
  }

  private static class CastingHandler implements InvocationHandler {

    private final Collection<?> objects;
    private final InvokeListener listener;

    public CastingHandler(Collection<?> objects) {
      this(objects, null);
    }

    public CastingHandler(Collection<?> objects, InvokeListener listener) {
      this.objects = objects;
      this.listener = listener;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      List<Object> results = new ArrayList<Object>();
      Object item = null;
      Object result;

      for (Iterator<?> iterator = objects.iterator(); iterator.hasNext();) {
        try {
          item = iterator.next();
          result = method.invoke(item, args);
          if (listener != null) {
            listener.afterExecute(item, method, args);
          }
          if (result != null) {
            results.add(result);
          }
        } catch (Throwable t) {
          // TODO MULE-863: What should we do if null?
          if (listener != null) {
            t = listener.onException(item, method, args, t);
            if (t != null) {
              throw t;
            }
          }
        }
      }
      return results;
    }
  }

  public static interface InvokeListener {

    void afterExecute(Object object, Method method, Object[] args);

    Throwable onException(Object object, Method method, Object[] args, Throwable t);
  }
}
